<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Documentation SimpleTest : les objets fantaisie partiels</title>
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
</head>
<body>
<div class="menu_back">
<div class="menu">
<a href="index.html">SimpleTest</a>
                |
                <a href="overview.html">Overview</a>
                |
                <a href="unit_test_documentation.html">Unit tester</a>
                |
                <a href="group_test_documentation.html">Group tests</a>
                |
                <a href="mock_objects_documentation.html">Mock objects</a>
                |
                <a href="partial_mocks_documentation.html">Partial mocks</a>
                |
                <a href="reporter_documentation.html">Reporting</a>
                |
                <a href="expectation_documentation.html">Expectations</a>
                |
                <a href="web_tester_documentation.html">Web tester</a>
                |
                <a href="form_testing_documentation.html">Testing forms</a>
                |
                <a href="authentication_documentation.html">Authentication</a>
                |
                <a href="browser_documentation.html">Scriptable browser</a>
</div>
</div>
<h1>Documentation sur les objets fantaisie partiels</h1>
        This page...
        <ul>
<li>
            <a href="#injection">Le probl&egrave;me de l'injection d'un objet fantaisie</a>.
        </li>
<li>
            D&eacute;placer la cr&eacute;ation vers une m&eacute;thode <a href="#creation">fabrique prot&eacute;g&eacute;e</a>.
        </li>
<li>
            <a href="#partiel">L'objet fantaisie partiel</a> g&eacute;n&egrave;re une sous-classe.
        </li>
<li>
            Les objets fantaisie partiels <a href="#moins">testent moins qu'une classe</a>.
        </li>
</ul>
<div class="content">
        
            <p>
                Un objet fantaisie partiel n'est ni plus ni moins qu'un mod&egrave;le de conception pour soulager un probl&egrave;me sp&eacute;cifique du test avec des objets fantaisie, celui de placer des objets fantaisie dans des coins serr&eacute;s. Il s'agit d'un outil assez limit&eacute; et peut-&ecirc;tre m&ecirc;me une id&eacute;e pas si bonne que &ccedil;a. Elle est incluse dans SimpleTest pour la simple raison que je l'ai trouv&eacute;e utile &agrave; plus d'une occasion et qu'elle m'a &eacute;pargn&eacute;e pas mal de travail dans ces moments-l&agrave;.
            </p>
        
        <p>
<a class="target" name="injection">
<h2>Le probl&egrave;me de l'injection dans un objet fantaisie</h2>
</a>
</p>
            <p>
                Quand un objet en utilise un autre il est tr&egrave;s simple d'y faire circuler une version fantaisie d&eacute;j&agrave; pr&ecirc;te avec ses attentes. Les choses deviennent un peu plus d&eacute;licates si un objet en cr&eacute;e un autre et que le cr&eacute;ateur est celui que l'on souhaite tester. Cela revient &agrave; dire que l'objet cr&eacute;&eacute; devrait &ecirc;tre une fantaisie, mais nous pouvons difficilement dire &agrave; notre classe sous test de cr&eacute;er un objet fantaisie plut&ocirc;t qu'un "vrai" objet. La classe test&eacute;e ne sait m&ecirc;me pas qu'elle travaille dans un environnement de test.
            </p>
            <p>
                Par exemple, supposons que nous sommes en train de construire un client telnet et qu'il a besoin de cr&eacute;er une socket r&eacute;seau pour envoyer ses messages. La m&eacute;thode de connexion pourrait ressemble &agrave; quelque chose comme...
<pre>
<strong>&lt;?php
    require_once('socket.php');
    
    class Telnet {
        ...
        function &amp;connect($ip, $port, $username, $password) {
            $socket = &amp;new Socket($ip, $port);
            $socket-&gt;read( ... );
            ...
        }
    }
?&gt;</strong>
</pre>
                Nous voudrions vraiment avoir une version fantaisie de l'objet socket, que pouvons nous faire ?
            </p>
            <p>
                La premi&egrave;re solution est de passer la socket en tant que param&egrave;tre, ce qui force la cr&eacute;ation au niveau inf&eacute;rieur. Charger le client de cette t&acirc;che est effectivement une bonne approche si c'est possible et devrait conduire &agrave; un remaniement -- de la cr&eacute;ation &agrave; partir de l'action. En fait, c'est l&agrave; une des mani&egrave;res avec lesquels tester en s'appuyant sur des objets fantaisie vous force &agrave; coder des solutions plus resserr&eacute;es sur leur objectif. Ils am&eacute;liorent votre programmation.
            </p>
            <p>
                Voici ce que &ccedil;a devrait &ecirc;tre...
<pre>
&lt;?php
    require_once('socket.php');
    
    class Telnet {
        ...
        <strong>function &amp;connect(&amp;$socket, $username, $password) {
            $socket-&gt;read( ... );
            ...
        }</strong>
    }
?&gt;
</pre>
                Sous-entendu, votre code de test est typique d'un cas de test avec un objet fantaisie.
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new Telnet();
        $telnet-&gt;connect($socket, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                C'est assez &eacute;vident que vous ne pouvez descendre que d'un niveau. Vous ne voudriez pas que votre application de haut niveau cr&eacute;e tous les fichiers de bas niveau, sockets et autres connexions &agrave; la base de donn&eacute;es dont elle aurait besoin. Elle ne conna&icirc;trait pas les param&egrave;tres du constructeur de toute fa&ccedil;on.
            </p>
            <p>
                La solution suivante est de passer l'objet cr&eacute;&eacute; sous la forme d'un param&egrave;tre optionnel...
<pre>
&lt;?php
    require_once('socket.php');
    
    class Telnet {
        ...<strong>
        function &amp;connect($ip, $port, $username, $password, $socket = false) {
            if (!$socket) {
                $socket = &amp;new Socket($ip, $port);
            }
            $socket-&gt;read( ... );</strong>
            ...
            return $socket;
        }
    }
?&gt;
</pre>
                Pour une solution rapide, c'est g&eacute;n&eacute;ralement suffisant. Ensuite le test est tr&egrave;s similaire : comme si le param&egrave;tre &eacute;tait transmis formellement...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new Telnet();
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret', &amp;$socket);
        ...</strong>
    }
}
</pre>
                Le probl&egrave;me de cette approche tient dans son manque de nettet&eacute;. Il y a du code de test dans la classe principale et aussi des param&egrave;tres transmis dans le sc&eacute;nario de test qui ne sont jamais utilis&eacute;s. Il s'agit l&agrave; d'une approche rapide et sale, mais qui ne reste pas moins efficace dans la plupart des situations.
            </p>
            <p>
                Une autre solution encore est de laisser un objet fabrique s'occuper de la cr&eacute;ation...
<pre>
&lt;?php
    require_once('socket.php');
    
    class Telnet {<strong>
        function Telnet(&amp;$network) {
            $this-&gt;_network = &amp;$network;
        }</strong>
        ...
        function &amp;connect($ip, $port, $username, $password) {<strong>
            $socket = &amp;$this-&gt;_network-&gt;createSocket($ip, $port);
            $socket-&gt;read( ... );</strong>
            ...
            return $socket;
        }
    }
?&gt;
</pre>
                Il s'agit l&agrave; probablement de la r&eacute;ponse la plus travaill&eacute;e &eacute;tant donn&eacute; que la cr&eacute;ation est maintenant situ&eacute;e dans une petite classe sp&eacute;cialis&eacute;e. La fabrique r&eacute;seau peut &ecirc;tre test&eacute;e s&eacute;par&eacute;ment et utilis&eacute;e en tant que fantaisie quand nous testons la classe telnet...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $network = &amp;new MockNetwork($this);
        $network-&gt;setReturnReference('createSocket', $socket);
        $telnet = &amp;new Telnet($network);
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                Le probl&egrave;me reste que nous ajoutons beaucoup de classes &agrave; la biblioth&egrave;que. Et aussi que nous utilisons beaucoup de fabriques ce qui rend notre code un peu moins intuitif. La solution la plus flexible, mais aussi la plus complexe.
            </p>
            <p>
                Peut-on trouver un juste milieu ?
            </p>
        
        <p>
<a class="target" name="creation">
<h2>M&eacute;thode fabrique prot&eacute;g&eacute;e</h2>
</a>
</p>
            <p>
                Il existe une technique pour palier &agrave; ce probl&egrave;me sans cr&eacute;er de nouvelle classe dans l'application; par contre elle induit la cr&eacute;ation d'une sous-classe au moment du test. Premi&egrave;rement nous d&eacute;pla&ccedil;ons la cr&eacute;ation de la socket dans sa propre m&eacute;thode...
<pre>
&lt;?php
    require_once('socket.php');
    
    class Telnet {
        ...
        function &amp;connect($ip, $port, $username, $password) {<strong>
            $socket = &amp;$this-&gt;_createSocket($ip, $port);</strong>
            $socket-&gt;read( ... );
            ...
        }<strong>
        
        function &amp;_createSocket($ip, $port) {
            return new Socket($ip, $port);
        }</strong>
    }
?&gt;
</pre>
                Il s'agit l&agrave; de la seule modification dans le code de l'application.
            </p>
            <p>
                Pour le sc&eacute;nario de test, nous devons cr&eacute;er une sous-classe de mani&egrave;re &agrave; intercepter la cr&eacute;ation de la socket...
<pre>
<strong>class TelnetTestVersion extends Telnet {
    var $_mock;
    
    function TelnetTestVersion(&amp;$mock) {
        $this-&gt;_mock = &amp;$mock;
        $this-&gt;Telnet();
    }
    
    function &amp;_createSocket() {
        return $this-&gt;_mock;
    }
}</strong>
</pre>
                Ici j'ai d&eacute;plac&eacute; la fantaisie dans le constructeur, mais un setter aurait fonctionn&eacute; tout aussi bien. Notez bien que la fantaisie est plac&eacute;e dans une variable d'objet avant que le constructeur ne soit attach&eacute;. C'est n&eacute;cessaire dans le cas o&ugrave; le constructeur appelle  <span class="new_code">connect()</span>. Autrement il pourrait donner un valeur nulle &agrave; partir de <span class="new_code">_createSocket()</span>.
            </p>
            <p>
                Apr&egrave;s la r&eacute;alisation de tout ce travail suppl&eacute;mentaire le sc&eacute;nario de test est assez simple. Nous avons juste besoin de tester notre nouvelle classe &agrave; la place...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new TelnetTestVersion($socket);
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                Cette nouvelle classe est tr&egrave;s simple bien s&ucirc;r. Elle ne fait qu'initier une valeur renvoy&eacute;e, &agrave; la mani&egrave;re d'une fantaisie. Ce serait pas mal non plus si elle pouvait v&eacute;rifier les param&egrave;tres entrants. Exactement comme un objet fantaisie. Il se pourrait bien que nous ayons &agrave; r&eacute;aliser cette astuce r&eacute;guli&egrave;rement : serait-il possible d'automatiser la cr&eacute;ation de cette sous-classe ?
            </p>
        
        <p>
<a class="target" name="partiel">
<h2>Un objet fantaisie partiel</h2>
</a>
</p>
            <p>
                Bien s&ucirc;r la r&eacute;ponse est "oui" ou alors j'aurais arr&ecirc;t&eacute; d'&eacute;crire depuis quelques temps d&eacute;j&agrave; ! Le test pr&eacute;c&eacute;dent a repr&eacute;sent&eacute; beaucoup de travail, mais nous pouvons g&eacute;n&eacute;rer la sous-classe en utilisant une approche &agrave; celle des objets fantaisie.
            </p>
            <p>
                Voici donc une version avec objet fantaisie partiel du test...
<pre>
<strong>Mock::generatePartial(
        'Telnet',
        'TelnetTestVersion',
        array('_createSocket'));</strong>

class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {<strong>
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new TelnetTestVersion($this);
        $telnet-&gt;setReturnReference('_createSocket', $socket);
        $telnet-&gt;Telnet();
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...</strong>
    }
}
</pre>
                La fantaisie partielle est une sous-classe de l'original dont on aurait "remplac&eacute;" les m&eacute;thodes s&eacute;lectionn&eacute;es avec des versions de test. L'appel &agrave; <span class="new_code">generatePartial()</span> n&eacute;cessite trois param&egrave;tres : la classe &agrave; sous classer, le nom de la nouvelle classe et une liste des m&eacute;thodes &agrave; simuler.
            </p>
            <p>
                Instancier les objets qui en r&eacute;sultent est plut&ocirc;t d&eacute;licat. L'unique param&egrave;tre du constructeur d'un objet fantaisie partiel est la r&eacute;f&eacute;rence du testeur unitaire. Comme avec les objets fantaisie classiques c'est n&eacute;cessaire pour l'envoi des r&eacute;sultats de test en r&eacute;ponse &agrave; la v&eacute;rification des attentes.
            </p>
            <p>
                Une nouvelle fois le constructeur original n'est pas lanc&eacute;. Indispensable dans le cas o&ugrave; le constructeur aurait besoin des m&eacute;thodes fantaisie : elles n'ont pas encore &eacute;t&eacute; initi&eacute;es ! Nous initions les valeurs retourn&eacute;es &agrave; cet instant et ensuite lan&ccedil;ons le constructeur avec ses param&egrave;tres normaux. Cette construction en trois &eacute;tapes de "new", suivie par la mise en place des m&eacute;thodes et ensuite par la lancement du constructeur proprement dit est ce qui distingue le code d'un objet fantaisie partiel.
            </p>
            <p>
                A part pour leur construction, toutes ces m&eacute;thodes fantaisie ont les m&ecirc;mes fonctionnalit&eacute;s que dans le cas des objets fantaisie et toutes les m&eacute;thodes non fantaisie se comportent comme avant. Nous pouvons mettre en place des attentes tr&egrave;s facilement...
<pre>
class TelnetTest extends UnitTestCase {
    ...
    function testConnection() {
        $socket = &amp;new MockSocket($this);
        ...
        $telnet = &amp;new TelnetTestVersion($this);
        $telnet-&gt;setReturnReference('_createSocket', $socket);<strong>
        $telnet-&gt;expectOnce('_createSocket', array('127.0.0.1', 21));</strong>
        $telnet-&gt;Telnet();
        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');
        ...<strong>
        $telnet-&gt;tally();</strong>
    }
}
</pre>
            </p>
        
        <p>
<a class="target" name="moins">
<h2>Tester moins qu'une classe</h2>
</a>
</p>
            <p>
                Les m&eacute;thodes issues d'un objet fantaisie n'ont pas besoin d'&ecirc;tre des m&eacute;thodes fabrique, Il peut s'agir de n'importe quelle sorte de m&eacute;thode. Ainsi les objets fantaisie partiels nous permettent de prendre le contr&ocirc;le de n'importe quelle partie d'une classe, le constructeur except&eacute;. Nous pourrions m&ecirc;me aller jusqu'&agrave; cr&eacute;er des fantaisies sur toutes les m&eacute;thodes &agrave; part celle que nous voulons effectivement tester.
            </p>
            <p>
                Cette situation est assez hypoth&eacute;tique, &eacute;tant donn&eacute; que je ne l'ai jamais essay&eacute;e. Je suis ouvert &agrave; cette possibilit&eacute;, mais je crains qu'en for&ccedil;ant la granularit&eacute; d'un objet on n'obtienne pas forc&eacute;ment un code de meilleur qualit&eacute;. Personnellement j'utilise les objets fantaisie partiels comme moyen de passer outre la cr&eacute;ation ou alors de temps en temps pour tester le mod&egrave;le de conception TemplateMethod.
            </p>
            <p>
                Pour choisir le m&eacute;canisme &agrave; utiliser, on en revient toujours aux standards de code de votre projet.
            </p>
        
    </div>
        References and related information...
        <ul>
<li>
            La page du projet SimpleTest sur <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
        </li>
<li>
            <a href="http://simpletest.sourceforge.net/">L'API compl&egrave;te pour SimpleTest</a> &agrave; partir de PHPDoc.
        </li>
<li>
            La m&eacute;thode fabrique prot&eacute;g&eacute;e est d&eacute;crite dans <a href="http://www-106.ibm.com/developerworks/java/library/j-mocktest.html">cet article d'IBM</a>. Il s'agit de l'unique papier formel que j'ai vu sur ce probl&egrave;me.
        </li>
</ul>
<div class="menu_back">
<div class="menu">
<a href="index.html">SimpleTest</a>
                |
                <a href="overview.html">Overview</a>
                |
                <a href="unit_test_documentation.html">Unit tester</a>
                |
                <a href="group_test_documentation.html">Group tests</a>
                |
                <a href="mock_objects_documentation.html">Mock objects</a>
                |
                <a href="partial_mocks_documentation.html">Partial mocks</a>
                |
                <a href="reporter_documentation.html">Reporting</a>
                |
                <a href="expectation_documentation.html">Expectations</a>
                |
                <a href="web_tester_documentation.html">Web tester</a>
                |
                <a href="form_testing_documentation.html">Testing forms</a>
                |
                <a href="authentication_documentation.html">Authentication</a>
                |
                <a href="browser_documentation.html">Scriptable browser</a>
</div>
</div>
<div class="copyright">
            Copyright<br>Marcus Baker 2006
        </div>
</body>
</html>
